﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using UCL.Powersource;
using UCL.Powersource.Analysis;

namespace PowersourceUIToolkit
{
    public static class AssemblySource
    {

        public enum IgnoredAssemblyReasons
        {
            Unknown,
            NotSigned,
            AlreadyLoaded,
            NothingToLoad,
            ErrorOnLoad,
            DoesNotExist
        }

        public class IgnoredAssemblyInfo
        {
            public string AssemblyFileName { get; private set; }
            public IgnoredAssemblyReasons Reason { get; private set; }

            internal IgnoredAssemblyInfo(string fn, IgnoredAssemblyReasons reason)
            {
                AssemblyFileName = fn;
                Reason = reason;
            }
        }

        /// <summary>
        /// The singleton instance of the AssemblySource used by the framework.
        /// </summary>
        public static ReadOnlyCollection<Assembly> Instance { get; private set; }

        private static readonly List<Assembly> _Instance = new List<Assembly>();

        public static ReadOnlyCollection<IgnoredAssemblyInfo> IgnoredAssemblyFileNames { get; private set; }

        private static readonly List<IgnoredAssemblyInfo> _IgnoredAssemblies = new List<IgnoredAssemblyInfo>();

        static AssemblySource()
        {
            Instance = new ReadOnlyCollection<Assembly>(_Instance);
            IgnoredAssemblyFileNames = new ReadOnlyCollection<IgnoredAssemblyInfo>(_IgnoredAssemblies);
        }

        internal static void AddFromType(Type tp)
        {
            if (_Instance.Contains(tp.Assembly)) return;
            _Instance.Add(tp.Assembly);
        }

        internal static bool ValidateAssemblyPKI(string assemblyFile)
        {
            var Log = LogManager.GetLog(typeof(AssemblySource));
            bool passTheValidation = false;
            X509Certificate executingCert = null;
            try
            {
                executingCert = X509Certificate.CreateFromSignedFile(assemblyFile);
                //TODO: Add validation code here  
                if (executingCert != null)
                    passTheValidation = true;
            }
            catch (Exception exc)
            {
                Log.Warn(exc, "Loading certificate info for the file {0} failed.", assemblyFile);
            }
            var issuer = "??";
            var hash = "??";
            var expDate = "??";
            try
            {
                var r = Assembly.LoadFrom(assemblyFile);
                var token = r.GetName().GetPublicKeyToken();
                //TODO: Pass PublicKey validation
                if (token != null) passTheValidation = true;

                if (executingCert != null)
                {

                    byte[] assemblyKey = executingCert.GetPublicKey();
                    issuer = executingCert.Issuer;
                    hash = executingCert.GetCertHashString();
                    expDate = executingCert.GetExpirationDateString();
                }

            }
            catch (Exception exc)
            {

                passTheValidation = false;
                Log.Error(exc, "While load public key from {0} an exception was thrown", assemblyFile);
            }
            Log.Info("Validation of assembly {0}: Result = {4} - {1}, expires on {2},hash: {3}", assemblyFile, issuer, expDate, hash, passTheValidation);
            return passTheValidation;
        }

        internal static void AddFromList(IEnumerable<string> ExtensionsToLoad, Bootstrapper bootStrapperToValidate = null)
        {
            var Log = LogManager.GetLog(typeof(AssemblySource));
            foreach (var assemblyFile in ExtensionsToLoad)
            {
                if (File.Exists(assemblyFile))
                {
                    FileInfo fi = new FileInfo(assemblyFile);
                    Log.Info("Trying to load extensibilities from {0} - {1}.", assemblyFile,fi.FullName);


                    bool allowedToAdd = false;
                    if (bootStrapperToValidate != null)
                    {
                        allowedToAdd = bootStrapperToValidate.ValidateAssembly(assemblyFile);
                    }
                    else
                    {
                        allowedToAdd = ValidateAssemblyPKI(assemblyFile);
                    }

                    Log.Info("File {0} validated = {1} ", assemblyFile, allowedToAdd);
                    if (allowedToAdd)
                    {

                        try
                        {
                            var toLoad = Assembly.ReflectionOnlyLoadFrom(fi.FullName);
                            var loadedAssembly = Assembly.Load(toLoad.GetName());
                            _Instance.Add(loadedAssembly);
                            
                            //var exportedTypes = toLoad.GetExportedTypes();

                            //bool loadAssembly = false;
                            //foreach (var exType in exportedTypes)
                            //{
                            //    foreach (var it in importableTypes)
                            //    {
                            //        if (it.IsAssignableFrom(exType))
                            //        {
                            //            loadAssembly = true;
                            //            break;
                            //        }
                            //    }
                            //    if (loadAssembly) break;
                            //}

                            //var alreadyLoaded = _Instance.FirstOrDefault(a => a.FullName == toLoad.FullName);
                            //if (alreadyLoaded != null)
                            //{
                            //    _IgnoredAssemblies.Add(new IgnoredAssemblyInfo(toLoad.FullName, IgnoredAssemblyReasons.AlreadyLoaded));
                            //    Log.Warn("Ignoring {0} - there is already assembly with the same fullname {1}.", assemblyFile, alreadyLoaded.FullName);
                            //}
                            //else
                            //    //if (!loadAssembly)
                            //    //{
                            //    //    _IgnoredAssemblies.Add(new IgnoredAssemblyInfo(toLoad.FullName, IgnoredAssemblyReasons.NothingToLoad));
                            //    //    Log.Warn("Ignoring {0} - there is nothing to load inside the assembly ", assemblyFile);
                            //    //}
                            //    //else
                            //    {
                            //        _Instance.Add();
                            //    }
                        }
                        catch (Exception exc)
                        {
                            Log.Error(exc, "Error when loaded {0}", assemblyFile);
                            _IgnoredAssemblies.Add(new IgnoredAssemblyInfo(assemblyFile, IgnoredAssemblyReasons.ErrorOnLoad));
                        }
                    }
                    else
                    {
                        _IgnoredAssemblies.Add(new IgnoredAssemblyInfo(assemblyFile, IgnoredAssemblyReasons.NotSigned));
                    }
                }
                else
                {
                    Log.Error(null, "File {0} does not exists.", assemblyFile);
                    _IgnoredAssemblies.Add(new IgnoredAssemblyInfo(assemblyFile, IgnoredAssemblyReasons.DoesNotExist));
                }
            }



        }

        internal static void AddFromBootStrapper(Bootstrapper bootstrapper)
        {
            _Instance.AddRange(bootstrapper.SelectAssemblies());
        }

        internal static void Clear()
        {
            _Instance.Clear();
            _IgnoredAssemblies.Clear();
        }
    }
}
